Skip to content

Fix key prop type #74

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Oct 23, 2022
Merged

Fix key prop type #74

merged 8 commits into from
Oct 23, 2022

Conversation

mununki
Copy link
Member

@mununki mununki commented Oct 21, 2022

This PR fixed #73

@mununki mununki mentioned this pull request Oct 21, 2022
src/React.res Outdated
external jsx: (component<'props>, 'props) => element = "jsx"
external jsxKeyed: (component<'props>, 'props, string) => element = "jsx"

let jsx = (~key=?, component, props) =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering whether this can be achieved directly with an external.
Btw where is the documentation that one can pass the key as third argument to the react jsxfunction?

Copy link
Member Author

@mununki mununki Oct 23, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

external jsxKeyed: (component<'props>, 'props, ~key: option<string>=?) => element = "jsx"

Like this? Shouldn't we have to move the labelled key argument position to first?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that can't be done because it needs to be before mandatory arguments.
Unless one adds a fake additional mandatory argument of type unit perhaps.

Copy link
Contributor

@cristianoc cristianoc Oct 23, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this work?

type was wrong_edited_to_fix_type_of_key

external jsx: (component<'props>, 'props, ~key: string=?, @ignore unit) => element = "jsx"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this work?

external jsx: (component<'props>, 'props, ~key: option<string>=?, @ignore unit) => element = "jsx"

I don't think it is working.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for cleanest generated output, one needs to expose 2 functions:
jsx without a key argument, and jsxKeyed with a mandatory 3rd argument of type option<string>.
Then at call site the PPX would call jsx when key is not passed, and would call jsxKeyed when key is passed.

Copy link
Member Author

@mununki mununki Oct 23, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw, I couldn't find the doc about the third argument as key, but I found it from the source code. https://github.com/facebook/react/blob/8e2bde6f2751aa6335f3cef488c05c3ea08e074a/packages/react/src/jsx/ReactJSXElement.js#L210

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the key argument needs to be optional not option<string> as mandatory. If it is option<string> as mandatory, then users should use it.

<C key=Some("k") />

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the key argument needs to be optional not option<string> as mandatory. If it is option<string> as mandatory, then users should use it.

<C key=Some("k") />

It does not have to be optional. The PPX can take care of passing the value in the appropriate way.
But it might be simpler to make it optional so the call PPX only needs to do one special thing: pass a final ().

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for cleanest generated output, one needs to expose 2 functions: jsx without a key argument, and jsxKeyed with a mandatory 3rd argument of type option<string>. Then at call site the PPX would call jsx when key is not passed, and would call jsxKeyed when key is passed.

This would be easy fix, I'll fix the binding and make a PR for PPX.

cristianoc
cristianoc previously approved these changes Oct 23, 2022
@mununki
Copy link
Member Author

mununki commented Oct 23, 2022

rescript-lang/syntax#693 (comment)

Maybe we need to revert the addKeyProp function back to avoid the manipulating props object which user made.

@cristianoc
Copy link
Contributor

Exploring the opportunity to use cloneElement. Assuming I understood how this works https://reactjs.org/docs/react-api.html#cloneelement

Instead of this:

React.createElementWithKey(~key="k", V4CA.make, {})

one would generate this:

React.cloneElement(React.createElement(V4CA.make, {}), {"key": "k"})

So one would rely on 2 pure bindings without any runtime in rescript-react.

@mununki
Copy link
Member Author

mununki commented Oct 23, 2022

Exploring the opportunity to use cloneElement. Assuming I understood how this works https://reactjs.org/docs/react-api.html#cloneelement

Instead of this:

React.createElementWithKey(~key="k", V4CA.make, {})

one would generate this:

React.cloneElement(React.createElement(V4CA.make, {}), {"key": "k"})

So one would rely on 2 pure bindings without any runtime in rescript-react.

Good idea!
But we need to consider the key as optional argument for this expression <C ?key />

@cknitt
Copy link
Member

cknitt commented Oct 23, 2022

So one would rely on 2 pure bindings without any runtime in rescript-react.

That's certainly an advantage, but it will be even more inefficient than the Object.assign solution, so we are moving away further and further from the "zero cost" idea.

Here is the implementation of cloneElement: https://github.com/facebook/react/blob/8e2bde6f2751aa6335f3cef488c05c3ea08e074a/packages/react/src/ReactElement.js#L486

@cknitt
Copy link
Member

cknitt commented Oct 23, 2022

I think we would only need to do make sure that the props object is copied in the

<A {...props} key />

case. (In JS, {...props} would copy the props object, too.)

Then we can use the

Obj.magic(props)["key"] = key

solution for all cases.

@cristianoc
Copy link
Contributor

React.createElement seems likely to become legacy over time. I would not worry too much about fine tuning corner cases, especially imperative update vs obj assign in the rare case where key is used. Perhaps just reverting the change, and always use obj assign is simplest.

For the jsx mode, that seems the way things will trend over time. And it makes sense to optimise the code generated in that case. In particular, it would be nice to introduce zero dependencies, so one can look at the generated code and understand it in isolation.

@cknitt
Copy link
Member

cknitt commented Oct 23, 2022

The case where key is used is actually not that rare, it basically needs to be used for every list you render (React.array), and there it will be used for each element.

But yes, if we view this as "legacy JSX transform support", then that's probably ok, as we do not have the same issue with key with the new JSX transform.

@mununki
Copy link
Member Author

mununki commented Oct 23, 2022

case. (In JS, {...props} would copy the props object, too.)

In this case, {...p} is not copy the object. The PPX uses just p as props in case without other props. So, this case p is not copied and manipulated.

@mununki
Copy link
Member Author

mununki commented Oct 23, 2022

React.createElement seems likely to become legacy over time. I would not worry too much about fine tuning corner cases, especially imperative update vs obj assign in the rare case where key is used. Perhaps just reverting the change, and always use obj assign is simplest.

For the jsx mode, that seems the way things will trend over time. And it makes sense to optimise the code generated in that case. In particular, it would be nice to introduce zero dependencies, so one can look at the generated code and understand it in isolation.

The case where key is used is actually not that rare, it basically needs to be used for every list you render (React.array), and there it will be used for each element.

But yes, if we view this as "legacy JSX transform support", then that's probably ok, as we do not have the same issue with key with the new JSX transform.

I agree you both. I'll revert the addKeyProp.

@mununki
Copy link
Member Author

mununki commented Oct 23, 2022

I think for cleanest generated output, one needs to expose 2 functions: jsx without a key argument, and jsxKeyed with a mandatory 3rd argument of type option<string>. Then at call site the PPX would call jsx when key is not passed, and would call jsxKeyed when key is passed.

This would be easy fix, I'll fix the binding and make a PR for PPX.

I've fixed it rescript-lang/syntax#694 234798b

CHANGELOG.md Outdated
@@ -1,5 +1,10 @@
# Changelog

## 0.11.0-rc.3

- Renamed `React.jsx(s)`, `ReactDOM.jsx(s)` to `React.jsx(s)NotKeyed`, `ReactDOM.jsx(s)NotKeyed`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs update.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for missing changelog and tests too often 😅
Updated b8fa0f3

@cristianoc cristianoc merged commit fb232ec into master Oct 23, 2022
@cristianoc cristianoc deleted the fix-key-type branch October 23, 2022 16:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Breaking change of key type
3 participants